import { ModuleConfig } from './module-config';
import { JavaFile } from './java-file'
import { listTemplates, Template } from '../template'
import { MysqlDialect } from '../db/mysql-dialect'
import { DbField } from '../db/db-field'
import { BeanStrategy, DefaultBeanStrategy } from './bean-strategy'
import { JavaField } from './java-field'
import { camelcase, capital, toClassName, toPackageName, toPropertyName } from '../util'
import { Func } from './func'
import * as path from 'path'
import { ResourceFile } from './resource-file'
import { IFile } from './base'

const javaTypeMapping = {
    int: 'Integer',
    smallint: 'Integer',
    float: 'Double',
    double: 'Double',
    longtext: 'String',
    varchar: 'String',
    char: 'String',
    bigint: 'Long',
    time: 'java.util.Date',
    timestamp: 'java.util.Date',
    date: 'java.util.Date',
    datetime: 'java.util.Date',
    decimal: 'java.math.BigDecimal',
}

function javaType(columnType: string, columnLen: number): string {
    switch (columnType) {
        case 'tinyint': {
            return columnLen === 1 ? 'Boolean' : 'Integer'
        }
    }

    const jt = javaTypeMapping[columnType]
    if (jt) {
        return jt
    }

    console.warn('unknown Java type', columnType, columnLen)
    return 'Object'
}

/*
 * 按功能点生成代码
 */
function generateFunction(tableName: string, tableFields: DbField[], beanStrategy: BeanStrategy) {
    const fields: JavaField[] = []
    for (const f of tableFields) {
        fields.push(new JavaField({
            isId: f.primary,
            name: camelcase(f.name),
            type: javaType(f.type, f.length)
        }))
    }
    return new Func({
        name: beanStrategy.camelName,
        beanFields: fields
    })
}

function generateJavaFile(func: Func, javaFile: JavaFile, template: Template, config: ModuleConfig, beanStrategy: BeanStrategy) {
    const ph = template.config.placeholder;
    const propertyNamePh = toPropertyName(ph)
    const classNamePh = toClassName(ph)
    const packageNamePh = toPackageName(ph)
    // console.log(propertyNamePh, classNamePh, packageNamePh)

    const moduleName = config.moduleName
    const javaSrc = 'src/main/java'
    const projectDir = path.resolve(config.projectRoot || '/')
    const packageBase = config.package + '.' + camelcase(moduleName).toLowerCase()
    const packageName = config.package + '.' + javaFile.package.replace(new RegExp(packageNamePh, 'i'), camelcase(moduleName).toLowerCase())
    const packagePath = packageName.replace(/\./g, '/')
    // console.log('projectDir', projectDir)
    // console.log('packageBase', packageBase)
    // console.log('packagePath', packageName, packagePath)

    let className = javaFile.className.replace(new RegExp(classNamePh, 'i'), beanStrategy.capitalName)

    // console.log('className', className)
    // console.log('filename', filename)
    // console.log(filePath)

    let content = javaFile.content
    // 替换换行符
    content = content.replace(/\r\n/g, '\n')
    // 替换package
    content = content.replace(new RegExp(`package.*${packageNamePh}.*;`), s => {
        return s.replace(new RegExp(packageNamePh, 'g'), packageBase)
    })
    // 替换import
    content = content.replace(new RegExp(`import.*${packageNamePh}.*;`, 'g'), s => {
        return s.replace(new RegExp(packageNamePh, 'g'), packageBase)
    })
    // 替换类名
    content = content.replace(new RegExp(classNamePh, 'g'), beanStrategy.capitalName)
    // 替换属性名
    content = content.replace(new RegExp(propertyNamePh, 'g'), beanStrategy.camelName)

    if (className === beanStrategy.entity) {
        // let jpa = false
        if (content.match(/@Table|@Entity/)) {
            // jpa = true
            content = content.replace(/@Table(\(.*\))?/, `@Table(name = "${beanStrategy.table}")`)
        }

        // const existImports = content.

        // TODO
        const imports: string[] = []
        const props: string[] = []
        for (const field of func.beanFields) {
            if (field.name === 'id') {
                continue
            }
            if (field.needImport) {
                imports.push(`import ${field.type};`)
            }
            props.push(`private ${field.simpleTypeName} ${field.name};`)
            // console.log('field', field)
        }

        // console.log('imports', imports)
        // console.log('properties', props)

        content = content.replace('/*content*/', props.join('\n    '))

        if (imports.length > 0) {
            const importStr = imports.join('\n')
            let idx = content.lastIndexOf('import')
            idx = content.indexOf(';', idx)
            content = content.slice(0, idx + 1) + '\n' + importStr + content.slice(idx + 1)
        }

        // console.log(content)
    }
    if (className === beanStrategy.application) {
        className = `${capital(camelcase(moduleName))}Application`
        content = content.replace(new RegExp(beanStrategy.application, 'g'), className)
    }

    const filename = `${className}.java`
    const filePath = path.resolve(projectDir, moduleName, javaSrc, packagePath, filename)
    // console.log('================================')

    return new JavaFile({
        className,
        content,
        package: packageName,
        path: filePath,
        filename
    })
}

export class JavaGenerator {
    private readonly config: ModuleConfig

    constructor(config: ModuleConfig) {
        this.config = config
    }

    public async generate(template: Template): Promise<IFile[]> {
        const dialect = new MysqlDialect(this.config);
        const javaRet: JavaFile[] = []
        const resourcesRet: ResourceFile[] = []
        const buildFileRet: ResourceFile[] = []
        try {
            const javaFiles = await template.getJavaFiles()
            const tables = await dialect.tables();
            for (const t of tables) {
                const beanStrategy = new DefaultBeanStrategy(t, this.config);
                const func = await generateFunction(t, await dialect.tableFields(t), beanStrategy)
                for (const java of javaFiles) {
                    const r = generateJavaFile(func, java, template, this.config, beanStrategy)
                    javaRet.push(r)
                }
            }

            const resourceFiles = await template.getResourceFiles()
            for (const f of resourceFiles) {
                const p = path.resolve(this.config.projectRoot, this.config.moduleName, 'src/main/resources', f.filename)
                resourcesRet.push(new ResourceFile({
                    path: p,
                    filename: f.filename,
                    content: f.content
                }))
            }

            const buildFiles = await template.getBuildFiles()
            for (const f of buildFiles) {
                const p = path.resolve(this.config.projectRoot, this.config.moduleName, f.filename)
                buildFileRet.push(new ResourceFile({
                    path: p,
                    filename: f.filename,
                    content: f.content
                }))
            }
        } finally {
            dialect.dispose()
        }
        return [...javaRet, ...resourcesRet, ...buildFileRet]
    }
}

async function test() {
    const generator = new JavaGenerator({
        package: 'org.misty.testproject',
        tableNamePrefix: 't_',
        password: '123456',
        database: 'spring-code-generator-test',
        projectRoot: 'f:/testProject',
        moduleName: 'test_module'
    });
    const temps = await listTemplates();

    await generator.generate(temps[0])
}

// test()
